package com.ocean.aws.husksync;

import com.ocean.aws.aw.AWBridge;
import com.ocean.aws.config.PluginConfig;
import com.ocean.aws.relay.SkinRelay;
import com.ocean.aws.util.CompressionUtils;
import net.william278.husksync.BukkitHuskSync;
import net.william278.husksync.HuskSync;
import net.william278.husksync.adapter.Adaptable;
import net.william278.husksync.api.HuskSyncAPI;
import net.william278.husksync.data.BukkitData;
import net.william278.husksync.data.Identifier;
import net.william278.husksync.data.Serializer;
import net.william278.husksync.event.BukkitDataSaveEvent;
import net.william278.husksync.event.BukkitPreSyncEvent;
import net.william278.husksync.event.BukkitSyncCompleteEvent;
import net.william278.husksync.user.BukkitUser;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.bukkit.event.player.PlayerQuitEvent;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.UUID;

/**
 * HuskSync 适配器骨架。
 * TODO: 使用 HuskSync 的 Developer API 注册自定义数据类型并在加载/保存时读写字节。
 */
public final class HuskSyncAdapter implements Listener {

    private final AWBridge bridge;
    private final boolean compress;
    private static HuskSyncAdapter INSTANCE;
    private final JavaPlugin plugin;
    private final PluginConfig config;
    private final SkinRelay relay;
    private final java.util.Set<java.util.UUID> appliedThisSync = java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>());

    public HuskSyncAdapter(JavaPlugin plugin, AWBridge bridge, boolean compress, PluginConfig config) {
        this.plugin = plugin;
        this.bridge = bridge;
        this.compress = compress;
        this.config = config;
        this.relay = config.skinRelayEnabled ? new SkinRelay(config.redisUri, config.redisUsername, config.redisPassword, config.skinKeyPrefix) : null;
    }

    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    public void onPreSync(BukkitPreSyncEvent event) {
        try {
            Bukkit.getLogger().info("HuskSync preSync: user=" + event.getUser().getUsername());
        } catch (Throwable ignored) {
            Bukkit.getLogger().info("HuskSync preSync: fired");
        }
        try { appliedThisSync.remove(event.getUser().getUuid()); } catch (Throwable ignored) {}
    }

    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    public void onSyncComplete(BukkitSyncCompleteEvent event) {
        try {
            Bukkit.getLogger().info("HuskSync syncComplete: user=" + event.getUser().getUsername());
        } catch (Throwable ignored) {
            Bukkit.getLogger().info("HuskSync syncComplete: fired");
        }
        if (relay != null) {
            java.util.UUID uuid = event.getUser().getUuid();
            if (!appliedThisSync.contains(uuid)) {
                try {
                    byte[] raw = relay.getLatestWardrobe(uuid);
                    if (raw != null) {
                        java.nio.file.Path objectsDir = deriveObjectsDir();
                        int restored = relay.ensureNodesFromWardrobeNbt(raw, objectsDir);
                        Player p = Bukkit.getPlayer(uuid);
                        if (p != null) {
                            bridge.importWardrobeCompressedBytes(p, raw);
                            Bukkit.getLogger().warning("AW skinRelay fallback applied: restoredNodes=" + restored);
                        }
                    }
                } catch (Throwable ignored) { }
            }
            appliedThisSync.remove(uuid);
        }
    }

    public void register() {
        HuskSyncAPI api = HuskSyncAPI.getInstance();
        var husk = api.getPlugin();
        INSTANCE = this;
        api.registerDataSerializer(KEY, new DataSerializer(husk));
        Bukkit.getPluginManager().registerEvents(this, plugin);
        Bukkit.getLogger().info("HuskSyncAdapter: data key registered -> " + KEY);
    }

    // 旧方法移除：统一走压缩 NBT 二进制

    // ---------------- HuskSync integration ----------------
    public static final Identifier KEY = Identifier.from("armourers_workshop_sync", "wardrobe");

    public static final class Data extends BukkitData implements Adaptable {
        public byte[] data;

        public Data(byte[] data) {
            this.data = data;
        }

        @SuppressWarnings("unused")
        private Data() {
        }

        @Override
        public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
            Player p = Bukkit.getPlayer(user.getUuid());
            if (p == null || data == null || data.length == 0) return;
            Bukkit.getScheduler().runTask(plugin, () -> {
                try {
                    HuskSyncAdapter ctx = INSTANCE;
                    if (ctx == null) return;
                    byte[] payload = ctx.compress ? CompressionUtils.ungzip(data) : data;
                    if (ctx.relay != null) {
                        try {
                            java.nio.file.Path objectsDir = ctx.deriveObjectsDir();
                            int restored = ctx.relay.ensureNodesFromWardrobeNbt(payload, objectsDir);
                            org.bukkit.Bukkit.getLogger().info("AW skinRelay apply: nodes restored=" + restored + ", objectsDir=" + objectsDir);
                            java.util.Set<String> ids = com.ocean.aws.relay.SkinRelay.extractNodeIds(payload);
                            org.bukkit.Bukkit.getLogger().info("AW skinRelay apply: extracted ids=" + ids);
                        } catch (Throwable ignore) {}
                    }
                    ctx.bridge.importWardrobeCompressedBytes(p, payload);
                    try { ctx.appliedThisSync.add(user.getUuid()); } catch (Throwable ignored) {}
                } catch (Throwable e) {
                    String userName;
                    try { userName = user.getUsername(); } catch (Throwable t) { userName = user.getUuid().toString(); }
                    String msg = e.getMessage() != null ? e.getMessage() : e.toString();
                    Bukkit.getLogger().warning("Failed to apply AW wardrobe for user=" + userName + ": " + msg);
                    String lower = msg.toLowerCase();
                    if (lower.contains("node") && lower.contains("not found")) {
                        Bukkit.getLogger().warning("AW skin node missing on this server. Please share or mirror the world/skin-database directory across servers so the referenced node exists.");
                    }
                }
            });
        }
    }

    public static final class DataSerializer implements Serializer<HuskSyncAdapter.Data> {
        private final Serializer<HuskSyncAdapter.Data> delegate;

        public DataSerializer(@NotNull HuskSync api) {
            this.delegate = new Json<HuskSyncAdapter.Data>(api, HuskSyncAdapter.Data.class);
        }

        @Override
        public HuskSyncAdapter.Data deserialize(@NotNull String serialized) throws DeserializationException {
            return delegate.deserialize(serialized);
        }

        @Override
        public @NotNull String serialize(@NotNull HuskSyncAdapter.Data element) throws SerializationException {
            return delegate.serialize(element);
        }
    }

    @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    public void onSave(BukkitDataSaveEvent event) {
        Player p = Bukkit.getPlayer(event.getUser().getUuid());
        if (p == null) return;
        event.editData(unpacked -> {
            try {
                byte[] raw = bridge.exportWardrobeCompressedBytes(p);
                if (relay != null) {
                    try {
                        java.nio.file.Path objectsDir = deriveObjectsDir();
                        int pushed = relay.collectAndPushFromWardrobeNbt(raw, objectsDir);
                        java.util.Set<String> ids = com.ocean.aws.relay.SkinRelay.extractNodeIds(raw);
                        org.bukkit.Bukkit.getLogger().info("AW skinRelay onSave: nodes pushed=" + pushed + ", ids=" + ids + ", objectsDir=" + objectsDir);
                        // store latest wardrobe snapshot for fallback
                        relay.setLatestWardrobe(event.getUser().getUuid(), raw);
                    } catch (Throwable ignore) {}
                }
                byte[] bytes = compress ? CompressionUtils.gzip(raw) : raw; // store bytes after optional gzip
                unpacked.setData(KEY, new Data(bytes));
            } catch (Exception e) {
                Bukkit.getLogger().warning("HuskSyncAdapter save failed: " + (e.getMessage() != null ? e.getMessage() : e.toString()));
            }
        });
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onQuit(PlayerQuitEvent event) throws Exception {
        Player p = event.getPlayer();
        UUID uuid = p.getUniqueId();

        // 1) 导出衣柜
        byte[] raw = bridge.exportWardrobeCompressedBytes(p);

        // 2) 现有 relay 逻辑可保留
        if (relay != null) {
            relay.setLatestWardrobe(uuid, raw);
            Path objectsDir = deriveObjectsDir();
            relay.collectAndPushFromWardrobeNbt(raw, objectsDir);
        }

        // 3) 显式保存快照（关键）
        HuskSyncAPI api = HuskSyncAPI.getInstance();
        byte[] bytes = compress ? CompressionUtils.gzip(raw) : raw;
        api.getUser(uuid).thenAccept(optUser -> optUser.ifPresent(user ->
                api.getCurrentData(user).thenAccept(optSnap -> {
                    net.william278.husksync.data.DataSnapshot.Unpacked unpacked = optSnap.orElseGet(() ->
                            net.william278.husksync.api.HuskSyncAPI.getInstance()
                                    .snapshotBuilder()
                                    .saveCause(net.william278.husksync.data.DataSnapshot.SaveCause.API)
                                    .build()
                    );
                    unpacked.setData(KEY, new HuskSyncAdapter.Data(bytes));
                    try { unpacked.setId(java.util.UUID.randomUUID()); } catch (Throwable ignored) {}
                    api.setCurrentData(user, unpacked);
                })
        ));
    }

    private java.nio.file.Path deriveObjectsDir() {
        try {
            var worlds = org.bukkit.Bukkit.getWorlds();
            java.io.File worldFolder = worlds.isEmpty() ? new java.io.File(org.bukkit.Bukkit.getWorldContainer(), "world") : worlds.get(0).getWorldFolder();
            return worldFolder.toPath().resolve("skin-database").resolve("objects").normalize();
        } catch (Throwable t) {
            return plugin.getDataFolder().toPath().resolve("..").resolve("world").resolve("skin-database").resolve("objects").normalize();
        }
    }
}


